Skip to content

feat: add Claude proxy target APIs with Ollama support#171

Merged
ymkiux merged 24 commits into
mainfrom
feat/claude-chat-completions-proxy
Jun 4, 2026
Merged

feat: add Claude proxy target APIs with Ollama support#171
ymkiux merged 24 commits into
mainfrom
feat/claude-chat-completions-proxy

Conversation

@awsl233777

@awsl233777 awsl233777 commented May 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add Claude proxy target API modes for Responses, Chat Completions, and Ollama.
  • Add config UI controls, badges, persistence, and startup/install wiring for the selected Claude target API.
  • Route Claude-compatible /v1/models and /v1/messages through the selected upstream API, including Anthropic → Ollama /api/chat conversion.
  • Preserve safer UI/runtime behavior around compact layouts, overlays, proxy start/stop, and rollback paths.

Real endpoint validation

Validated the Ollama target mode against a provided real Ollama endpoint without writing to the user's real ~/.codex config:

  • Created a temporary worktree and temporary Claude proxy settings file.
  • Started the built-in Claude proxy with targetApi: 'ollama' and a direct upstream base URL.
  • Verified GET /v1/models routed through the proxy to upstream /api/tags and returned the expected Ollama cloud model list.
  • Verified POST /v1/messages routed through the proxy to upstream /api/chat and converted the response back into an Anthropic-style message:
    • minimax-m3:cloud succeeded with a non-empty response when max_tokens was raised to 128.
    • gemma4:31b-cloud succeeded with a non-empty response.
  • Verified a forbidden upstream model returns a mapped Anthropic error instead of a false success.

Observed edge case: minimax-m3:cloud can return only message.thinking with an empty message.content when max_tokens / Ollama num_predict is too small (for example 32 with a system prompt). This is upstream model behavior; practical configs should avoid very small max token limits for that model.

Validation

  • GitHub checks are green for this PR, including the Node 18/20/22 CI matrix and CodeRabbit review.
  • Local real-endpoint proxy validation passed for the Ollama target mode as described above.
  • Attempted to run the full local unit runner from the temporary worktree, but the local environment was missing zip-lib; that failure was an environment dependency issue, not an Ollama proxy-path failure.
  • Temporary script, worktree, and branch were removed after validation; no local proxy/node process was left running.

Summary by CodeRabbit

  • New Features

    • Selectable Claude Target API (responses, chat_completions, ollama) in UI and CLI; built-in Claude proxy adds Chat Completions and Ollama modes and exposes runtime/version status.
  • Improvements

    • Config editor saves/normalizes targetApi, Ollama may omit API key, apply/rollback behavior is safer, apply responses include mode/target/updated keys, app-update notice and version-status check with localized text.
  • Tests

    • Expanded unit/e2e coverage for proxy modes, sharing, streaming, errors, and rollback.

@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@awsl233777, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 22 minutes and 3 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b9f2d649-9de5-421f-8c41-c42ca43934fc

📥 Commits

Reviewing files that changed from the base of the PR and between f9f12b8 and 1bc29bd.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (41)
  • .github/workflows/release.yml
  • README.md
  • README.zh.md
  • cli.js
  • cli/claude-proxy.js
  • cli/update.js
  • package.json
  • tests/e2e/test-claude-proxy.js
  • tests/e2e/test-claude.js
  • tests/unit/claude-proxy-adapter.test.mjs
  • tests/unit/claude-settings-sync.test.mjs
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/install-target-cards.test.mjs
  • tests/unit/provider-share-command.test.mjs
  • tests/unit/release-changelog.test.mjs
  • tests/unit/run.mjs
  • tests/unit/update-version-status.test.mjs
  • tests/unit/web-ui-behavior-parity.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • tools/release/changelog.js
  • web-ui/app.js
  • web-ui/logic.claude.mjs
  • web-ui/logic.runtime.mjs
  • web-ui/modules/app.methods.claude-config.mjs
  • web-ui/modules/app.methods.index.mjs
  • web-ui/modules/app.methods.install.mjs
  • web-ui/modules/app.methods.session-actions.mjs
  • web-ui/modules/app.methods.session-timeline.mjs
  • web-ui/modules/app.methods.startup-claude.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/partials/index/layout-header.html
  • web-ui/partials/index/modals-basic.html
  • web-ui/partials/index/panel-config-claude.html
  • web-ui/partials/index/panel-config-codex.html
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/styles/controls-forms.css
  • web-ui/styles/layout-shell.css
  • web-ui/styles/responsive.css
📝 Walkthrough

Walkthrough

Adds canonical Claude targetApi support and mode-aware proxy routing (responses, chat_completions, ollama); refactors CLI apply/share lifecycle to handle proxy tokens and rollback; threads targetApi through web UI state, validation, and rendering; adds version-status/update-notice flow; and expands unit and e2e tests.

Changes

Claude Proxy Dual-Mode Routing with targetApi

Layer / File(s) Summary
Proxy adapters and message conversion
cli/claude-proxy.js, tests/unit/claude-proxy-adapter.test.mjs
Adds image helpers, bridge/Ollama block filtering, extends Anthropic→responses mapping, and implements chat_completions and Ollama request builders and decoders plus SSE thinking handling and adapter tests.
Mode-aware proxy runtime & upstream routing
cli/claude-proxy.js, tests/e2e/test-claude-proxy.js
Normalizes targetApi, resolves direct and optional OpenAI-bridge upstreams, enforces mode constraints, selects upstream paths/headers by target, reports mode in health/status, and dispatches appropriate builders/decoders; e2e covers bridge startup, error passthrough, and header propagation.
CLI apply/share lifecycle and schema
cli.js, tests/unit/provider-share-command.test.mjs
Adds targetApi defaults and normalization, refactors applyToClaudeSettings to async proxy lifecycle with token/listenUrl writes and rollback-on-failure, introduces --target-api parsing, relaxes API-key requirement for Ollama, updates share payload/commands, and updates MCP schema conditional validation.
fetchLatestVersion & update-status flow
cli/update.js, cli.js, web-ui/modules/app.methods.install.mjs
Adds timeout-aware fetchLatestVersion, new version-status web action, install methods for version comparison and loadAppVersionStatus, startup silent check hook, UI methods, styles and tests to surface update notices.
Web UI: Claude config state, validation, modals, rendering
web-ui/app.js, web-ui/logic.claude.mjs, web-ui/modules/app.methods.claude-config.mjs, web-ui/modules/app.methods.startup-claude.mjs, web-ui/partials/*, web-ui/res/*
Threads targetApi through normalization/merge/validation (aliases → chat_completions), exempts apiKey for Ollama, persists targetApi in apply payloads/dedupe keys, adds modal selects and provider badges, i18n strings, and tests.
Unit & E2E tests for modes and rollback
tests/e2e/*, tests/unit/*
Adds upstream chat/completions mock and tests, proxy restart/apply/rollback e2e scenarios for chat_completions and ollama, unit tests for request/response conversions and SSE events, and parity/test updates for new UI keys and layout changes.
UI layout heuristics & overlay tweak
web-ui/logic.runtime.mjs, web-ui/modules/app.methods.session-timeline.mjs, web-ui/styles/controls-forms.css
Removes UA-based compact-mode forcing, updates call sites, converts .tool-config-write-overlay to fixed full-viewport with safe-area padding, adds autocomplete="off" on permission checkboxes, and minor template nesting tweak.

Estimated code review effort:
🎯 4 (Complex) | ⏱️ ~60 minutes

"I hopped the code and spun a tiny proxy,
Responses, Chat, and Ollama — one rabbit, three toys,
Tokens born locally, rollbacks kept tight,
UI picks the target, badges gleam in the light,
The rabbit cheers — dual-mode proxy runs right!"

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/claude-chat-completions-proxy

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
tests/unit/web-ui-logic.test.mjs (1)

84-87: ⚡ Quick win

Cover canonical chat_completions in normalization test.

This test validates two aliases but not the canonical value itself; adding it will better lock the API contract.

Proposed test addition
 test('normalizeClaudeConfig accepts chat completions target api aliases', () => {
+    assert.strictEqual(normalizeClaudeConfig({ targetApi: 'chat_completions' }).targetApi, 'chat_completions');
     assert.strictEqual(normalizeClaudeConfig({ targetApi: 'chat/completions' }).targetApi, 'chat_completions');
     assert.strictEqual(normalizeClaudeConfig({ targetApi: 'chat-completions' }).targetApi, 'chat_completions');
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/web-ui-logic.test.mjs` around lines 84 - 87, Add an assertion to
the existing test that covers the canonical value: call normalizeClaudeConfig
with targetApi: 'chat_completions' and assert its targetApi remains
'chat_completions' so the test verifies aliases plus the canonical form; update
the test block in tests/unit/web-ui-logic.test.mjs around the
normalizeClaudeConfig assertions to include this additional assertion
referencing normalizeClaudeConfig and targetApi.
tests/unit/claude-settings-sync.test.mjs (1)

480-513: ⚡ Quick win

Add a preservation case for non-default targetApi during merge.

Current test only validates default responses; it doesn’t catch regressions where an existing chat_completions value is unintentionally reset on edit.

Proposed additional assertion
 test('mergeClaudeConfig preserves externalCredentialType across edits without api key', () => {
@@
     assert.deepStrictEqual(merged, {
         apiKey: '',
         baseUrl: 'https://api.anthropic.com/',
         model: 'claude-3-7-sonnet',
         hasKey: true,
         externalCredentialType: 'auth-token',
         targetApi: 'responses'
     });
+
+    const mergedChat = mergeClaudeConfig.call(context, {
+        apiKey: '',
+        baseUrl: 'https://api.anthropic.com',
+        model: 'claude-3-7-sonnet',
+        hasKey: true,
+        externalCredentialType: 'auth-token',
+        targetApi: 'chat_completions'
+    }, {
+        model: 'claude-sonnet-4-6'
+    });
+    assert.strictEqual(mergedChat.targetApi, 'chat_completions');
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/claude-settings-sync.test.mjs` around lines 480 - 513, The test
currently only verifies that mergeClaudeConfig preserves defaults
(targetApi='responses') and misses regressions where an existing non-default
targetApi (e.g., 'chat_completions') is lost on merge; update the test for
mergeClaudeConfig in tests/unit/claude-settings-sync.test.mjs to include an
input/initial config with targetApi set to a non-default value (such as
'chat_completions') and assert that the merged result retains that explicit
targetApi rather than being reset to 'responses'; use the same
context.normalizeClaudeConfig and the mergeClaudeConfig function reference to
craft the call and expected assertion so the test will fail if mergeClaudeConfig
incorrectly overwrites non-default targetApi.
tests/e2e/test-claude-proxy.js (1)

335-342: ⚡ Quick win

Use predicate-based request matching instead of positional indexes.

Index-based checks can become flaky if an extra upstream chat call is introduced (retry, preflight, future hook). Matching by payload shape keeps this test stable.

Proposed assertion hardening
 const upstreamChatMessages = upstream.requests.filter((item) => item.path === '/v1/chat/completions');
 assert(upstreamChatMessages.length >= 2, 'claude proxy should hit upstream /v1/chat/completions');
-assert(upstreamChatMessages[0].headers.authorization === 'Bearer sk-claude-upstream', 'claude proxy chat should use provider auth for upstream');
-assert(upstreamChatMessages[0].body.messages[0].role === 'system', 'claude proxy chat should map system prompt to system message');
-assert(upstreamChatMessages[0].body.max_tokens === 128, 'claude proxy chat should map max_tokens to max_tokens');
-assert(upstreamChatMessages[0].body.stream === false, 'claude proxy chat should synthesize Anthropic streaming locally');
-assert(upstreamChatMessages[1].body.tool_choice.function.name === 'lookup', 'claude proxy chat should map tool_choice');
+const plainChatReq = upstreamChatMessages.find((item) =>
+    item.body && item.body.model === 'DeepSeek-V4-pro' && !item.body.tools
+);
+const toolChatReq = upstreamChatMessages.find((item) =>
+    item.body && Array.isArray(item.body.tools) && item.body.tools.length > 0
+);
+assert(plainChatReq && plainChatReq.headers.authorization === 'Bearer sk-claude-upstream', 'claude proxy chat should use provider auth for upstream');
+assert(plainChatReq.body.messages[0].role === 'system', 'claude proxy chat should map system prompt to system message');
+assert(plainChatReq.body.max_tokens === 128, 'claude proxy chat should map max_tokens to max_tokens');
+assert(plainChatReq.body.stream === false, 'claude proxy chat should synthesize Anthropic streaming locally');
+assert(toolChatReq && toolChatReq.body.tool_choice.function.name === 'lookup', 'claude proxy chat should map tool_choice');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/test-claude-proxy.js` around lines 335 - 342, The tests are using
positional indexes on upstreamChatMessages which is flaky; update the assertions
to locate requests by predicate on their payload shape instead of array
position: use upstream.requests.filter(...) or find(...) to match by
headers.authorization === 'Bearer sk-claude-upstream' for the provider-auth
check, match a request whose body.messages[0].role === 'system' and
body.max_tokens === 128 and body.stream === false for the
system/max_tokens/stream assertions, and match a request whose
body.tool_choice?.function?.name === 'lookup' for the tool_choice assertion;
replace the index-based assertions that reference upstreamChatMessages[0] and
[1] with these predicate-found entries and assert they exist and have the
expected properties.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web-ui/partials/index/modals-basic.html`:
- Around line 136-143: Replace the hardcoded Chinese label, option texts and
hint for the target API selector with i18n keys rendered through t(...);
specifically update the strings inside the select block bound to
newClaudeConfig.targetApi (the label "目标 API", the two option texts "Anthropic /
OpenAI Responses" and "OpenAI Chat Completions (/v1/chat/completions)", and the
form-hint copy) to use translation keys (e.g., t('claude.targetApi.label'),
t('claude.targetApi.option.responses'),
t('claude.targetApi.option.chat_completions'), t('claude.targetApi.hint')) and
ensure the same change is applied to the duplicated block around the other
selector (the block you modified at the later occurrence). Ensure keys exist in
the locale files.

In `@web-ui/partials/index/panel-config-claude.html`:
- Line 132: The subtitle text inside the div with class "card-subtitle" (the
element gated by v-if="config.targetApi === 'chat_completions'") is hardcoded;
replace it with a localized string by calling the translation helper (e.g.,
t('panel.mode.openai_chat_completions')) so it follows the app locale, and add
the corresponding translation key to the i18n resources; ensure the
component/context provides the t(...) function before using it.

In `@web-ui/res/web-ui-render.precompiled.js`:
- Around line 1890-1894: The hardcoded UI strings shown when checking
config.targetApi (e.g., the "OpenAI Chat Completions" subtitle rendered in the
_createElementBlock with class "card-subtitle", plus the field label, option
labels and hint around the same conditional branches at the other locations)
must be replaced with i18n keys and rendered via _ctx.t(...). Update the three
affected render sites to call _ctx.t('key_name') instead of raw text, add
appropriate i18n keys for the card subtitle, field label, option labels and hint
(use descriptive keys like "card.subtitle.openai_chat_completions",
"field.label.target_api", "option.label.x", "field.hint.target_api"), and ensure
the conditional branches still pick the right key based on config.targetApi so
translations are used for language switching.

---

Nitpick comments:
In `@tests/e2e/test-claude-proxy.js`:
- Around line 335-342: The tests are using positional indexes on
upstreamChatMessages which is flaky; update the assertions to locate requests by
predicate on their payload shape instead of array position: use
upstream.requests.filter(...) or find(...) to match by headers.authorization ===
'Bearer sk-claude-upstream' for the provider-auth check, match a request whose
body.messages[0].role === 'system' and body.max_tokens === 128 and body.stream
=== false for the system/max_tokens/stream assertions, and match a request whose
body.tool_choice?.function?.name === 'lookup' for the tool_choice assertion;
replace the index-based assertions that reference upstreamChatMessages[0] and
[1] with these predicate-found entries and assert they exist and have the
expected properties.

In `@tests/unit/claude-settings-sync.test.mjs`:
- Around line 480-513: The test currently only verifies that mergeClaudeConfig
preserves defaults (targetApi='responses') and misses regressions where an
existing non-default targetApi (e.g., 'chat_completions') is lost on merge;
update the test for mergeClaudeConfig in
tests/unit/claude-settings-sync.test.mjs to include an input/initial config with
targetApi set to a non-default value (such as 'chat_completions') and assert
that the merged result retains that explicit targetApi rather than being reset
to 'responses'; use the same context.normalizeClaudeConfig and the
mergeClaudeConfig function reference to craft the call and expected assertion so
the test will fail if mergeClaudeConfig incorrectly overwrites non-default
targetApi.

In `@tests/unit/web-ui-logic.test.mjs`:
- Around line 84-87: Add an assertion to the existing test that covers the
canonical value: call normalizeClaudeConfig with targetApi: 'chat_completions'
and assert its targetApi remains 'chat_completions' so the test verifies aliases
plus the canonical form; update the test block in
tests/unit/web-ui-logic.test.mjs around the normalizeClaudeConfig assertions to
include this additional assertion referencing normalizeClaudeConfig and
targetApi.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6ae0fbd9-703c-4ef4-b64e-cefdd78b6682

📥 Commits

Reviewing files that changed from the base of the PR and between 9ffba03 and 4b24338.

📒 Files selected for processing (13)
  • cli.js
  • cli/claude-proxy.js
  • tests/e2e/test-claude-proxy.js
  • tests/unit/claude-proxy-adapter.test.mjs
  • tests/unit/claude-settings-sync.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/app.js
  • web-ui/logic.claude.mjs
  • web-ui/modules/app.methods.claude-config.mjs
  • web-ui/modules/app.methods.startup-claude.mjs
  • web-ui/partials/index/modals-basic.html
  • web-ui/partials/index/panel-config-claude.html
  • web-ui/res/web-ui-render.precompiled.js
📜 Review details
🔇 Additional comments (10)
cli.js (1)

293-294: LGTM!

Also applies to: 5492-5494

cli/claude-proxy.js (2)

136-151: LGTM!

Also applies to: 238-352, 432-505, 1268-1270


644-647: LGTM!

Also applies to: 676-683, 757-784, 810-813, 1015-1015, 1041-1041, 1074-1077, 1080-1084, 1097-1099, 1198-1199, 1247-1247

web-ui/logic.claude.mjs (1)

72-75: LGTM!

Also applies to: 82-83

web-ui/app.js (1)

278-279: LGTM!

Also applies to: 284-285, 292-294, 539-542

web-ui/modules/app.methods.claude-config.mjs (1)

55-57: LGTM!

Also applies to: 67-69, 88-88, 110-110, 186-186, 208-210

web-ui/modules/app.methods.startup-claude.mjs (1)

250-252: LGTM!

tests/unit/claude-proxy-adapter.test.mjs (1)

7-10: LGTM!

Also applies to: 67-105, 145-172

tests/unit/web-ui-logic.test.mjs (1)

54-55: LGTM!

Also applies to: 66-80

tests/e2e/test-claude-proxy.js (1)

101-142: LGTM!

Also applies to: 260-334, 343-394

Comment thread web-ui/partials/index/modals-basic.html
Comment thread web-ui/partials/index/panel-config-claude.html Outdated
Comment thread web-ui/res/web-ui-render.precompiled.js Outdated
@awsl233777

Copy link
Copy Markdown
Collaborator Author

Updated the PR to close the Claude Chat Completions apply-path gap and the UI i18n review comment.

Changes in this push:

  • Replaced hardcoded Claude target API UI strings with i18n keys and regenerated the precompiled Web UI render.
  • When a Claude config uses targetApi: chat_completions, apply-claude-config now starts the local Claude-compatible proxy and writes ~/.claude/settings.json to that local proxy URL instead of writing the upstream URL directly.
  • The local proxy uses the applied Claude config's upstream baseUrl/key as its direct upstream, so this works even when the Claude config name is not a Codex provider name.
  • Restoring/applying responses mode stops the Claude proxy and resets its saved target API to avoid state leakage into later proxy starts.
  • Added E2E coverage for the direct-Claude-config upstream path and the local-proxy settings URL behavior.

Validation run locally:

  • node -c cli.js
  • node -c cli/claude-proxy.js
  • npm run test:unit (All 533 tests passed)
  • npm run test:e2e
  • npm run lint
  • git diff --check

@coderabbitai re-review

@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

@awsl233777 Thanks for the detailed update! The changes look comprehensive — closing the apply-path gap for chat_completions mode (routing through the local proxy and writing the proxy URL to ~/.claude/settings.json), cleaning up state on responses mode restore, the i18n pass on the UI strings, and the expanded E2E coverage all sound solid. Triggering a full re-review now.

✅ Actions performed

Full review triggered.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cli.js (1)

9176-9214: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Rollback proxy runtime on post-start failure paths.

If the proxy starts successfully but a later step fails (e.g., reading/writing Claude settings), the function returns an error while leaving the proxy running. That creates inconsistent state (apply failed, runtime still active).

Proposed fix
 async function applyToClaudeSettings(config = {}) {
+    let proxyStarted = false;
     try {
@@
         if (targetApi === 'chat_completions') {
             await stopBuiltinClaudeProxyRuntime();
             proxyResult = await startBuiltinClaudeProxyRuntime({
@@
             if (!proxyResult || proxyResult.error || proxyResult.success === false || !proxyResult.listenUrl) {
+                await stopBuiltinClaudeProxyRuntime();
                 return {
                     success: false,
                     mode: 'claude-proxy',
                     error: (proxyResult && proxyResult.error) || '启动 Claude 兼容代理失败'
                 };
             }
+            proxyStarted = true;
             settingsBaseUrl = proxyResult.listenUrl;
             settingsApiKey = 'codexmate';
         } else {
@@
         const readResult = readJsonObjectFromFile(CLAUDE_SETTINGS_FILE, {});
         if (!readResult.ok) {
+            if (proxyStarted) {
+                await stopBuiltinClaudeProxyRuntime();
+            }
             return { success: false, mode: 'settings-file', error: readResult.error };
         }
@@
     } catch (e) {
+        if (proxyStarted) {
+            try { await stopBuiltinClaudeProxyRuntime(); } catch (_) { }
+        }
         return {
             success: false,
             mode: 'settings-file',
             error: e.message || '应用 Claude 配置失败'
         };
     }
 }

Also applies to: 9262-9267

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli.js` around lines 9176 - 9214, The code may leave the built-in Claude
proxy running if later steps fail; ensure we stop the proxy on all post-start
failure paths by calling stopBuiltinClaudeProxyRuntime() before any early return
after a successful start (i.e., when proxyResult indicates success and you later
hit an error while handling CLAUDE_SETTINGS_FILE, readJsonObjectFromFile, or
writeJsonAtomic). Specifically, after using startBuiltinClaudeProxyRuntime() and
assigning settingsBaseUrl/settingsApiKey (and likewise in the branch that writes
BUILTIN_CLAUDE_PROXY_SETTINGS_FILE), add logic to call
stopBuiltinClaudeProxyRuntime() (or otherwise tear down the runtime) whenever
you are about to return an error, so proxyResult/started runtimes are cleaned up
consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cli.js`:
- Around line 9178-9197: The code currently sets a predictable proxy credential
(settingsApiKey = 'codexmate') after calling startBuiltinClaudeProxyRuntime,
which makes the proxy guessable if bound to a non-loopback address; replace this
by generating a cryptographically-secure random token for settingsApiKey (e.g.,
using crypto.randomBytes) and assign that token instead of the constant, and
additionally validate proxyResult.listenUrl (from
startBuiltinClaudeProxyRuntime) to ensure it is bound to a loopback address
(127.0.0.1/::1); if the listenUrl is non-loopback either refuse to use a
fixed/predictable key (fail or require explicit config) or log and rotate a
random token, ensuring settingsBaseUrl uses proxyResult.listenUrl and only the
random token is exposed as the API key.

---

Outside diff comments:
In `@cli.js`:
- Around line 9176-9214: The code may leave the built-in Claude proxy running if
later steps fail; ensure we stop the proxy on all post-start failure paths by
calling stopBuiltinClaudeProxyRuntime() before any early return after a
successful start (i.e., when proxyResult indicates success and you later hit an
error while handling CLAUDE_SETTINGS_FILE, readJsonObjectFromFile, or
writeJsonAtomic). Specifically, after using startBuiltinClaudeProxyRuntime() and
assigning settingsBaseUrl/settingsApiKey (and likewise in the branch that writes
BUILTIN_CLAUDE_PROXY_SETTINGS_FILE), add logic to call
stopBuiltinClaudeProxyRuntime() (or otherwise tear down the runtime) whenever
you are about to return an error, so proxyResult/started runtimes are cleaned up
consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f1c4589c-b7ff-4435-a393-681c33a08def

📥 Commits

Reviewing files that changed from the base of the PR and between 4b24338 and 9c40b1a.

📒 Files selected for processing (9)
  • cli.js
  • cli/claude-proxy.js
  • tests/e2e/test-claude.js
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/modules/app.methods.claude-config.mjs
  • web-ui/modules/i18n.dict.mjs
  • web-ui/partials/index/modals-basic.html
  • web-ui/partials/index/panel-config-claude.html
  • web-ui/res/web-ui-render.precompiled.js
✅ Files skipped from review due to trivial changes (1)
  • web-ui/modules/i18n.dict.mjs
📜 Review details
🔇 Additional comments (22)
tests/e2e/test-claude.js (1)

67-91: LGTM!

web-ui/partials/index/panel-config-claude.html (1)

132-132: Ensure claude.targetApi.chatCompletionsBadge is present in i18n resources.

claude.targetApi.chatCompletionsBadge is defined in web-ui/modules/i18n.dict.mjs for all supported locale blocks (zh, ja, en), so the t('claude.targetApi.chatCompletionsBadge') call won’t fail due to a missing key.

web-ui/res/web-ui-render.precompiled.js (1)

1894-1894: LGTM!

Also applies to: 5561-5571, 5675-5685

tests/unit/web-ui-logic.test.mjs (1)

84-88: LGTM!

web-ui/partials/index/modals-basic.html (1)

136-143: LGTM!

Also applies to: 175-182

cli.js (1)

9154-9159: LGTM!

Also applies to: 9399-9403, 11008-11008, 15556-15558

cli/claude-proxy.js (10)

136-151: LGTM!


238-293: LGTM!


295-352: LGTM!


432-505: LGTM!


816-840: LGTM!


1212-1212: LGTM!


1099-1111: LGTM!


526-617: LGTM!


681-684: LGTM!


757-785: LGTM!

web-ui/modules/app.methods.claude-config.mjs (6)

50-59: LGTM!


61-72: LGTM!


85-89: LGTM!


110-127: LGTM!


186-199: LGTM!


202-211: LGTM!

Comment thread cli.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cli.js (1)

9176-9267: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Stop the proxy again if the later settings write path fails.

After Line 9178 starts the runtime, any later failure in readJsonObjectFromFile, backupFileIfNeededOnce, or writeJsonAtomic returns an error but never tears the proxy back down. That leaves a leaked background process and persisted proxy state that no longer matches ~/.claude/settings.json.

🧯 Suggested rollback
 async function applyToClaudeSettings(config = {}) {
     try {
         const apiKey = (config.apiKey || '').trim();
         if (!apiKey) {
             return { success: false, mode: 'settings-file', error: '请先输入 API Key' };
@@
         const targetApi = normalizeClaudeTargetApi(config.targetApi);
         let settingsBaseUrl = baseUrl;
         let settingsApiKey = apiKey;
         let proxyResult = null;
+        let proxyStarted = false;
@@
             proxyResult = await startBuiltinClaudeProxyRuntime({
                 enabled: true,
                 provider: typeof config.name === 'string' ? config.name.trim() : '',
                 authSource: 'provider',
                 targetApi,
@@
             if (!proxyResult || proxyResult.error || proxyResult.success === false || !proxyResult.listenUrl) {
                 return {
                     success: false,
                     mode: 'claude-proxy',
                     error: (proxyResult && proxyResult.error) || '启动 Claude 兼容代理失败'
                 };
             }
+            proxyStarted = true;
             settingsBaseUrl = proxyResult.listenUrl;
             settingsApiKey = 'codexmate';
@@
     } catch (e) {
+        if (proxyStarted) {
+            try {
+                await stopBuiltinClaudeProxyRuntime();
+            } catch (_) { }
+        }
         return {
             success: false,
             mode: 'settings-file',
             error: e.message || '应用 Claude 配置失败'
         };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli.js` around lines 9176 - 9267, The code can leak a running proxy if
startBuiltinClaudeProxyRuntime succeeds but later steps (readJsonObjectFromFile,
backupFileIfNeededOnce, writeJsonAtomic) fail; modify the flow around
startBuiltinClaudeProxyRuntime/stopBuiltinClaudeProxyRuntime so that when
proxyResult indicates a proxy was started you always stop it on any early return
or exception—either wrap the post-proxy logic in a try/finally that calls
stopBuiltinClaudeProxyRuntime() in the finally, or track a boolean like
proxyStarted and call stopBuiltinClaudeProxyRuntime() before each error return
and in the catch block; ensure this covers the branches that return after
readJsonObjectFromFile errors and after writeJsonAtomic/backup failures, and
still preserve returning proxy info only when the operation completes
successfully.
♻️ Duplicate comments (1)
cli.js (1)

9178-9196: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Force loopback before writing the fixed proxy token.

startBuiltinClaudeProxyRuntime(...) is called without a host override, so a previously persisted non-loopback bind can still be reused here. Line 9196 then writes a constant ANTHROPIC_API_KEY, which makes the proxy credential guessable off-box if this runtime comes up anywhere other than loopback.

🔒 Minimal hardening
             proxyResult = await startBuiltinClaudeProxyRuntime({
                 enabled: true,
+                host: DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS.host,
                 provider: typeof config.name === 'string' ? config.name.trim() : '',
                 authSource: 'provider',
                 targetApi,
                 timeoutMs: DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS.timeoutMs,
                 upstreamProviderName: typeof config.name === 'string' ? config.name.trim() : '',
                 upstreamBaseUrl: baseUrl,
                 upstreamApiKey: apiKey
             });
             if (!proxyResult || proxyResult.error || proxyResult.success === false || !proxyResult.listenUrl) {
                 return {
                     success: false,
                     mode: 'claude-proxy',
                     error: (proxyResult && proxyResult.error) || '启动 Claude 兼容代理失败'
                 };
             }
+            {
+                const listenHost = new URL(proxyResult.listenUrl).hostname;
+                if (listenHost !== '127.0.0.1' && listenHost !== 'localhost' && listenHost !== '::1') {
+                    await stopBuiltinClaudeProxyRuntime();
+                    return {
+                        success: false,
+                        mode: 'claude-proxy',
+                        error: 'Claude 兼容代理在 chat_completions 模式下必须仅监听 loopback'
+                    };
+                }
+            }
             settingsBaseUrl = proxyResult.listenUrl;
             settingsApiKey = 'codexmate';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli.js` around lines 9178 - 9196, The code currently starts the Claude proxy
without forcing a loopback bind and then writes a fixed proxy API key
('codexmate'/ANTHROPIC_API_KEY), which can leak if the runtime binds
non-loopback; update the call to startBuiltinClaudeProxyRuntime to force a
loopback host (e.g., pass bindHost: '127.0.0.1' or equivalent) and only persist
or expose a generated/ephemeral settingsApiKey when proxyResult.listenUrl is
loopback; replace the constant 'codexmate' assignment to settingsApiKey with a
securely generated random token (or skip persisting the token) when
proxyResult.listenUrl is not loopback so the credential cannot be guessed
off-box (references: startBuiltinClaudeProxyRuntime, settingsApiKey,
ANTHROPIC_API_KEY).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cli/claude-proxy.js`:
- Around line 769-776: resolveOpenaiBridgeUpstream may return null/undefined
causing a TypeError when accessing bridgeUpstream.baseUrl; update the logic
around resolveOpenaiBridgeUpstream (the bridgeUpstream variable) to explicitly
check for null/undefined and return an error if so, then only access
bridgeUpstream.baseUrl (used to set bridgeBaseUrl) after confirming
bridgeUpstream is an object and has a baseUrl property; keep existing
isValidHttpUrl(providerName) check and error messages using providerName
unchanged.

---

Outside diff comments:
In `@cli.js`:
- Around line 9176-9267: The code can leak a running proxy if
startBuiltinClaudeProxyRuntime succeeds but later steps (readJsonObjectFromFile,
backupFileIfNeededOnce, writeJsonAtomic) fail; modify the flow around
startBuiltinClaudeProxyRuntime/stopBuiltinClaudeProxyRuntime so that when
proxyResult indicates a proxy was started you always stop it on any early return
or exception—either wrap the post-proxy logic in a try/finally that calls
stopBuiltinClaudeProxyRuntime() in the finally, or track a boolean like
proxyStarted and call stopBuiltinClaudeProxyRuntime() before each error return
and in the catch block; ensure this covers the branches that return after
readJsonObjectFromFile errors and after writeJsonAtomic/backup failures, and
still preserve returning proxy info only when the operation completes
successfully.

---

Duplicate comments:
In `@cli.js`:
- Around line 9178-9196: The code currently starts the Claude proxy without
forcing a loopback bind and then writes a fixed proxy API key
('codexmate'/ANTHROPIC_API_KEY), which can leak if the runtime binds
non-loopback; update the call to startBuiltinClaudeProxyRuntime to force a
loopback host (e.g., pass bindHost: '127.0.0.1' or equivalent) and only persist
or expose a generated/ephemeral settingsApiKey when proxyResult.listenUrl is
loopback; replace the constant 'codexmate' assignment to settingsApiKey with a
securely generated random token (or skip persisting the token) when
proxyResult.listenUrl is not loopback so the credential cannot be guessed
off-box (references: startBuiltinClaudeProxyRuntime, settingsApiKey,
ANTHROPIC_API_KEY).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7f3075cd-6ca8-427d-85f1-4f2a07df91b4

📥 Commits

Reviewing files that changed from the base of the PR and between 9ffba03 and 9c40b1a.

📒 Files selected for processing (15)
  • cli.js
  • cli/claude-proxy.js
  • tests/e2e/test-claude-proxy.js
  • tests/e2e/test-claude.js
  • tests/unit/claude-proxy-adapter.test.mjs
  • tests/unit/claude-settings-sync.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/app.js
  • web-ui/logic.claude.mjs
  • web-ui/modules/app.methods.claude-config.mjs
  • web-ui/modules/app.methods.startup-claude.mjs
  • web-ui/modules/i18n.dict.mjs
  • web-ui/partials/index/modals-basic.html
  • web-ui/partials/index/panel-config-claude.html
  • web-ui/res/web-ui-render.precompiled.js
📜 Review details
🔇 Additional comments (22)
web-ui/partials/index/panel-config-claude.html (1)

132-132: LGTM!

web-ui/logic.claude.mjs (1)

72-75: LGTM!

Also applies to: 82-83

web-ui/app.js (1)

278-278: LGTM!

Also applies to: 284-284, 292-294, 539-542

web-ui/partials/index/modals-basic.html (1)

136-143: LGTM!

Also applies to: 175-182

web-ui/modules/i18n.dict.mjs (1)

1051-1055: LGTM!

Also applies to: 2112-2116, 3183-3187

tests/unit/claude-settings-sync.test.mjs (1)

490-492: LGTM!

Also applies to: 511-513

tests/unit/claude-proxy-adapter.test.mjs (1)

7-7: LGTM!

Also applies to: 9-9, 67-105, 145-172

tests/unit/web-ui-logic.test.mjs (1)

54-55: LGTM!

Also applies to: 67-68, 79-80, 84-88

tests/e2e/test-claude.js (1)

67-84: LGTM!

Also applies to: 90-92

tests/e2e/test-claude-proxy.js (1)

101-142: LGTM!

Also applies to: 260-384, 392-394

web-ui/modules/app.methods.startup-claude.mjs (1)

250-252: LGTM!

web-ui/res/web-ui-render.precompiled.js (1)

1890-1900: LGTM!

Also applies to: 5560-5572, 5674-5686

web-ui/modules/app.methods.claude-config.mjs (1)

55-56: LGTM!

Also applies to: 67-68, 88-88, 110-110, 112-112, 186-186, 188-188, 208-209

cli.js (1)

293-294: LGTM!

Also applies to: 5492-5494, 9154-9159, 9399-9403, 11007-11008, 15556-15564

cli/claude-proxy.js (8)

136-151: LGTM!


238-293: LGTM!


295-352: LGTM!


432-505: LGTM!


644-695: LGTM!


816-839: LGTM!


1100-1125: LGTM!


1224-1224: LGTM!

Also applies to: 1273-1273, 1294-1296

Comment thread cli/claude-proxy.js
@awsl233777

Copy link
Copy Markdown
Collaborator Author

Pushed a follow-up hardening commit for the latest CodeRabbit actionable comments: 3501aca.

Changes:

  • The Claude Chat Completions apply path now writes a generated random local proxy token into ~/.claude/settings.json instead of a predictable fixed token.
  • The proxy is explicitly bound to the default loopback host for this apply path.
  • If the local proxy starts but the later Claude settings read/write path fails, the function rolls the proxy runtime and saved proxy mode back to the safe responses/off state.
  • OpenAI bridge upstream resolution now handles null/undefined bridge results without throwing.
  • Updated E2E assertions so Chat Completions mode verifies a random local proxy token and confirms the upstream key is not written into Claude Code settings.

Validation run locally after this commit:

  • node -c cli.js
  • node -c cli/claude-proxy.js
  • npm run test:unit (All 533 tests passed)
  • npm run test:e2e
  • npm run lint
  • git diff --check

@coderabbitai re-review

@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

@awsl233777 Thanks for the thorough hardening commit! The changes look well-targeted:

  • Random proxy token — eliminates the predictability concern with the fixed 'codexmate' token.
  • Explicit loopback binding — good defense-in-depth to ensure the proxy isn't inadvertently exposed on a broader interface.
  • Rollback on settings write failure — important atomicity improvement; avoids leaving the proxy running in an inconsistent state.
  • Null/undefined bridge guard — prevents a potential runtime throw in the upstream resolution path.
  • Updated E2E assertions — confirms the random token behavior and that the upstream key is kept out of Claude Code settings.

Triggering a full re-review now.

🐇✨

✅ Actions performed

Full review triggered.

@awsl233777

Copy link
Copy Markdown
Collaborator Author

Systematic test pass added for the Claude chat_completions flow.

What changed in this push:

  • Added e2e coverage that verifies apply-claude-config binds the generated Claude proxy settings to loopback.
  • Added e2e coverage that restoring responses mode stops the local Claude proxy and resets saved proxy targetApi back to responses.
  • Added rollback coverage: if Claude settings cannot be read during chat_completions apply, the API fails cleanly and the proxy runtime/settings are not left half-applied.
  • Added OpenAI bridge edge coverage: if the bridge upstream entry is missing, claude-proxy-start returns an explicit error and does not start the runtime.

Validation run locally:

  • npm run test:e2e
  • npm run test:unit ✅ — 533 tests passed
  • npm run lint ✅ — 200 files passed

Additional real-upstream smoke:

  • Used the locally configured telepub token without printing it.
  • Verified chat_completions apply starts the local Claude proxy and writes Claude settings to a random local proxy token rather than the upstream key.
  • The actual completion request reached the upstream path but returned upstream 429, so I am not marking real completion generation as successful.

@coderabbitai re-review

@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

@awsl233777 Thanks for the systematic test pass! The new coverage looks well-rounded:

  • Loopback binding assertion — confirms the proxy is bound to 127.0.0.1 and not exposed on a broader interface.
  • Responses-mode restore — verifies the proxy is stopped and targetApi is reset, preventing state leakage across proxy starts.
  • Rollback coverage — ensures a clean failure path when Claude settings can't be read mid-apply (no half-applied state left behind).
  • Bridge edge case — explicit error return (rather than a silent throw) when the bridge upstream entry is missing.

Triggering a full re-review now. 🐇✨

✅ Actions performed

Full review triggered.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cli.js (1)

9216-9251: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Delay proxy shutdown until the settings write succeeds.

In the responses branch, Lines 9217-9218 tear down the existing proxy before CLAUDE_SETTINGS_FILE is even read. If the later read/write fails, apply returns an error but the previously working proxy is already gone.

Suggested fix
         if (targetApi === 'chat_completions') {
             await stopBuiltinClaudeProxyRuntime();
             const proxyToken = crypto.randomBytes(24).toString('hex');
             proxyResult = await startBuiltinClaudeProxyRuntime({
                 enabled: true,
                 host: DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS.host,
                 provider: typeof config.name === 'string' ? config.name.trim() : '',
                 authSource: 'provider',
                 targetApi,
                 timeoutMs: DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS.timeoutMs,
                 upstreamProviderName: typeof config.name === 'string' ? config.name.trim() : '',
                 upstreamBaseUrl: baseUrl,
                 upstreamApiKey: apiKey
             });
             if (!proxyResult || proxyResult.error || proxyResult.success === false || !proxyResult.listenUrl) {
                 await stopBuiltinClaudeProxyRuntime();
                 resetBuiltinClaudeProxySavedSettingsToResponses();
                 return {
                     success: false,
                     mode: 'claude-proxy',
                     error: (proxyResult && proxyResult.error) || '启动 Claude 兼容代理失败'
                 };
             }
             proxyStarted = true;
             settingsBaseUrl = proxyResult.listenUrl;
             settingsApiKey = proxyToken;
-        } else {
-            await stopBuiltinClaudeProxyRuntime();
-            resetBuiltinClaudeProxySavedSettingsToResponses();
         }
@@
         ensureDir(CLAUDE_DIR);
         const backupPath = backupFileIfNeededOnce(CLAUDE_SETTINGS_FILE);
         writeJsonAtomic(CLAUDE_SETTINGS_FILE, nextSettings);
+        if (targetApi !== 'chat_completions') {
+            await stopBuiltinClaudeProxyRuntime();
+            resetBuiltinClaudeProxySavedSettingsToResponses();
+        }
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/e2e/test-claude-proxy.js`:
- Around line 356-370: Wrap the block that mutates and tests the bridge settings
in a try...finally so the original file is always restored: move the
fs.writeFileSync(bridgeSettingsPath, JSON.stringify({ providers: {} }, ...)) and
the subsequent api('claude-proxy-start') / asserts into a try block and put
fs.writeFileSync(bridgeSettingsPath, savedBridgeSettings, 'utf-8') inside a
finally block; reference the bridgeSettingsPath and savedBridgeSettings
variables and ensure the api calls missingBridgeStartResult and
missingBridgeStatus remain inside the try so any thrown error still triggers the
finally restoration.

In `@tests/e2e/test-claude.js`:
- Around line 99-109: The test corrupts the Claude settings.json
(claudeSettingsPath) but only restores it at the end, so failures leave the file
corrupted; wrap the mutation and subsequent assertions (the call to
api('apply-claude-config') and the checks against failedChatApply and
claudeProxyStatusAfterFailedApply from api('claude-proxy-status')) in a
try/finally and always write back validClaudeSettings to claudeSettingsPath in
the finally block to guarantee restoration even if an assertion throws.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5ad0bbee-40c7-483e-83ae-36c3bd62ac49

📥 Commits

Reviewing files that changed from the base of the PR and between 9c40b1a and 7b49467.

📒 Files selected for processing (4)
  • cli.js
  • cli/claude-proxy.js
  • tests/e2e/test-claude-proxy.js
  • tests/e2e/test-claude.js
📜 Review details
🔇 Additional comments (3)
cli.js (2)

9161-9172: LGTM!


9192-9215: ⚡ Quick win

Remove the “keep proxyToken and ANTHROPIC_API_KEY in sync” requirement

proxyToken is only generated in cli.js and written to env.ANTHROPIC_API_KEY, but startBuiltinClaudeProxyRuntime(...) is called without it. The proxy’s incoming authentication is driven by process.env.CODEXMATE_HTTP_TOKEN and checks Authorization / x-codexmate-token headers (not x-api-key/ANTHROPIC_API_KEY), so Claude’s ANTHROPIC_API_KEY value doesn’t need to match proxyToken for the proxy to accept requests.

			> Likely an incorrect or invalid review comment.
cli/claude-proxy.js (1)

769-771: LGTM!

Comment thread tests/e2e/test-claude-proxy.js Outdated
Comment thread tests/e2e/test-claude.js Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web-ui/app.js (1)

534-543: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard malformed persisted Claude config entries before assigning targetApi.

On Line 540, config.targetApi = ... assumes config is an object. Corrupted localStorage entries can throw here and break startup normalization.

Suggested fix
 for (const [name, config] of Object.entries(this.claudeConfigs)) {
+    if (!config || typeof config !== 'object' || Array.isArray(config)) {
+        delete this.claudeConfigs[name];
+        continue;
+    }
     if (config.apiKey && config.apiKey.includes('****')) {
         config.apiKey = '';
         config.hasKey = false;
     }
     const targetApiRaw = typeof config.targetApi === 'string' ? config.targetApi.trim().toLowerCase() : '';
     config.targetApi = targetApiRaw === 'chat_completions' || targetApiRaw === 'chat-completions' || targetApiRaw === 'chat/completions'
         ? 'chat_completions'
         : 'responses';
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/app.js` around lines 534 - 543, The loop over this.claudeConfigs
assumes each entry is a valid object; guard against malformed persisted entries
by checking that config is a non-null object before reading or writing
properties like config.apiKey, config.hasKey and config.targetApi, and if an
entry is invalid either skip normalization or replace it with a safe default
object (e.g., { apiKey: '', hasKey: false, targetApi: 'responses' }) so the
subsequent assignment to config.targetApi cannot throw.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cli.js`:
- Around line 9183-9203: The code allows targetApi === 'chat_completions' to
startBuiltinClaudeProxyRuntime even when config.baseUrl is empty (falling back
to the Anthropic Messages URL), which silently wires the proxy to the wrong
upstream; update the validation around normalizeClaudeTargetApi/targetApi and
baseUrl before calling startBuiltinClaudeProxyRuntime so that if targetApi ===
'chat_completions' and config.baseUrl is missing or equal to the default
Anthropic messages URL you either throw/return a validation error or call
startBuiltinClaudeProxyRuntime without upstreamBaseUrl (omit the upstreamBaseUrl
property) so the runtime resolves the upstream from config.name; adjust the
block that prepares proxyResult (the call to startBuiltinClaudeProxyRuntime and
the upstreamBaseUrl/upstreamProviderName fields) accordingly to enforce this
check.
- Around line 9192-9203: The generated proxyToken from
applyToClaudeSettings(...) is not passed into startBuiltinClaudeProxyRuntime,
causing an auth mismatch; update the call to startBuiltinClaudeProxyRuntime (the
invocation that currently sets host, provider, authSource, targetApi, timeoutMs,
upstreamProviderName, upstreamBaseUrl, upstreamApiKey) to include the generated
proxyToken (e.g., as an authToken or proxyToken parameter) so the runtime
enforces the same token that was written into Claude’s ANTHROPIC_API_KEY, and
also verify cli/claude-proxy.js checks that token (or falls back to
CODEXMATE_HTTP_TOKEN only when appropriate) for non-loopback requests when
DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS.host is non-loopback. Ensure parameter
naming matches startBuiltinClaudeProxyRuntime’s signature and update any
doc/comment accordingly.

---

Outside diff comments:
In `@web-ui/app.js`:
- Around line 534-543: The loop over this.claudeConfigs assumes each entry is a
valid object; guard against malformed persisted entries by checking that config
is a non-null object before reading or writing properties like config.apiKey,
config.hasKey and config.targetApi, and if an entry is invalid either skip
normalization or replace it with a safe default object (e.g., { apiKey: '',
hasKey: false, targetApi: 'responses' }) so the subsequent assignment to
config.targetApi cannot throw.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 858ec6da-e7ea-42fc-b114-09db6512dd0d

📥 Commits

Reviewing files that changed from the base of the PR and between 9ffba03 and 7b49467.

📒 Files selected for processing (15)
  • cli.js
  • cli/claude-proxy.js
  • tests/e2e/test-claude-proxy.js
  • tests/e2e/test-claude.js
  • tests/unit/claude-proxy-adapter.test.mjs
  • tests/unit/claude-settings-sync.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/app.js
  • web-ui/logic.claude.mjs
  • web-ui/modules/app.methods.claude-config.mjs
  • web-ui/modules/app.methods.startup-claude.mjs
  • web-ui/modules/i18n.dict.mjs
  • web-ui/partials/index/modals-basic.html
  • web-ui/partials/index/panel-config-claude.html
  • web-ui/res/web-ui-render.precompiled.js
📜 Review details
🔇 Additional comments (9)
cli.js (1)

293-294: LGTM!

Also applies to: 5492-5494, 9154-9172, 9417-9421, 11025-11026

tests/e2e/test-claude-proxy.js (1)

356-370: Always restore bridge settings with try/finally.

Line 358 mutates codexmate-openai-bridge.json, but restore on Line 370 is not guaranteed if an assertion or API call fails first.

tests/e2e/test-claude.js (1)

99-109: Wrap Claude settings corruption/restore in try/finally.

Line 101 corrupts settings.json, but restoration on Line 109 is skipped if intermediate assertions fail.

web-ui/modules/app.methods.claude-config.mjs (1)

55-56: LGTM!

Also applies to: 67-68, 88-88, 110-113, 186-189, 208-209

web-ui/modules/app.methods.startup-claude.mjs (1)

250-251: LGTM!

web-ui/modules/i18n.dict.mjs (1)

1051-1055: LGTM!

Also applies to: 2112-2116, 3183-3187

web-ui/partials/index/modals-basic.html (1)

136-143: LGTM!

Also applies to: 175-182

web-ui/partials/index/panel-config-claude.html (1)

132-132: LGTM!

web-ui/res/web-ui-render.precompiled.js (1)

1890-1900: LGTM!

Also applies to: 5560-5572, 5674-5686

Comment thread cli.js Outdated
Comment thread cli.js
…letions-proxy

# Conflicts:
#	cli.js
#	web-ui/modules/app.methods.claude-config.mjs
#	web-ui/modules/i18n.dict.mjs
#	web-ui/res/web-ui-render.precompiled.js
@awsl233777

Copy link
Copy Markdown
Collaborator Author

Resolved the main merge conflicts and pushed the merge commit 0629d0a.

What changed in this push:

  • Merged origin/main into feat/claude-chat-completions-proxy and resolved the conflicting Claude config apply path so the Chat Completions proxy mode is preserved alongside the new tool-config write guard.
  • Kept the split i18n locale structure from main and added the missing claude.targetApi.* locale keys for zh/en/ja/vi.
  • Regenerated web-ui/res/web-ui-render.precompiled.js from the current Web UI template instead of hand-editing the generated artifact.
  • Updated tests for the merged behavior: edit-modal state now preserves the default targetApi, and the Claude proxy e2e bridge provider includes the now-required provider model.

Local validation run on Node 22.22.1:

  • git diff --check
  • npm run ci:lint → passed
  • npm run ci:test → passed (test:unit + test:e2e)

@coderabbitai re-review

@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor

@awsl233777 Thanks for the merge conflict resolution! The changes look well-handled:

  • Conflict resolution — the Chat Completions proxy mode is preserved alongside the tool-config write guard from main.
  • i18n completeness — adding the claude.targetApi.* keys for all four locales (zh/en/ja/vi) ensures no missing translation fallbacks.
  • Precompiled render regeneration — generating from the template rather than hand-editing avoids drift between the source partials and the artifact.
  • Test updates — preserving default targetApi in edit-modal state and adding the required model field to the bridge e2e provider keeps the test suite consistent with the merged behavior.

Triggering a full re-review now. 🐇✨

✅ Actions performed

Full review triggered.

@awsl233777

Copy link
Copy Markdown
Collaborator Author

Minimized the PR diff with 059aad2 by restoring .gitignore to match main; this removes the unrelated trailing-blank-line noise from the PR.

No runtime code changed in this follow-up. The effective PR diff is now 18 files, limited to the Claude Chat Completions proxy path, required Web UI/i18n integration, generated render output, and regression tests.

Previous local validation still applies for the code-bearing merge resolution:

  • git diff --check
  • npm run ci:lint
  • npm run ci:test

@coderabbitai re-review

@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor

@awsl233777 Thanks for the cleanup — restoring .gitignore to match main is a good call to keep the diff focused on the actual feature changes. Triggering a full re-review of the now-trimmed 18-file diff. 🐇✨

✅ Actions performed

Full review triggered.

@awsl233777

Copy link
Copy Markdown
Collaborator Author

Updated this PR with the full follow-up pass:

  • Added/finished Claude built-in conversion for OpenAI Responses, OpenAI Chat Completions, and Ollama /api/chat.
  • Preserved supported text/image/tool payloads and stopped forwarding bridge-only incompatible blocks like thinking / document as fake user text.
  • Added the Claude UI target API option for Ollama in add/edit config flows.
  • Fixed compact-layout detection so mobile UA alone does not make desktop-width Web UI diverge.
  • Centered Codex/Claude write-lock overlays against the viewport.

Validation actually run:

  • npm run test:unit — 583 tests passed.
  • npm run lint — passed for 210 files.
  • Real Ollama smoke test against 27.154.56.162:11434: /api/tags reachable; gemma4:31b-cloud /api/chat returned codexmate-ollama-ok through this PR's Claude→Ollama→Anthropic conversion. Other listed cloud models were either subscription-gated (403) or emitted only thinking with empty content, so I did not count those as passing samples.
  • Headless Chrome Web UI checks: desktop UA/viewport, iPhone UA with desktop viewport, narrow touch compact mode, Codex/Claude overlay centering, and Ollama option presence.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

@awsl233777 Thanks for the substantial follow-up pass! The additions look well-scoped:

  • Ollama /api/chat conversion — extending the Claude proxy to support a third wire format alongside Responses and Chat Completions is a meaningful capability addition.
  • Thinking/document block filtering — stopping the forwarding of bridge-incompatible blocks as fake user text is an important correctness fix; those blocks would likely cause unexpected behavior or errors at the upstream.
  • Ollama UI option — surfacing targetApi for Ollama in the add/edit config flows keeps the UI consistent with the new backend path.
  • Compact-layout detection fix — preventing mobile UA alone from triggering compact mode on a desktop-width viewport is a good UX fix.
  • Overlay centering — viewport-relative centering for the Codex/Claude write-lock overlays is a visual polish improvement.

The test count growth (533 → 583) and the real Ollama smoke test (including the codexmate-ollama-ok roundtrip) provide good confidence. Triggering a full review now. 🐇

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cli.js (1)

15816-15829: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make the MCP schema match the runtime's ollama rules.

applyToClaudeSettings() allows targetApi: 'ollama' without an API key, but this MCP tool still requires apiKey unconditionally. That blocks the new Ollama mode for MCP clients even though the backend supports it.

Suggested fix
         inputSchema: {
-            type: 'object',
-            properties: {
-                apiKey: { type: 'string' },
-                baseUrl: { type: 'string' },
-                model: { type: 'string' },
-                name: { type: 'string' },
-                targetApi: { type: 'string' }
-            },
-            required: ['apiKey'],
-            additionalProperties: false
+            oneOf: [
+                {
+                    type: 'object',
+                    properties: {
+                        baseUrl: { type: 'string' },
+                        model: { type: 'string' },
+                        name: { type: 'string' },
+                        targetApi: { enum: ['ollama'] }
+                    },
+                    additionalProperties: false
+                },
+                {
+                    type: 'object',
+                    properties: {
+                        apiKey: { type: 'string' },
+                        baseUrl: { type: 'string' },
+                        model: { type: 'string' },
+                        name: { type: 'string' },
+                        targetApi: { type: 'string' }
+                    },
+                    required: ['apiKey'],
+                    additionalProperties: false
+                }
+            ]
         },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli.js` around lines 15816 - 15829, The MCP tool's inputSchema for action
'codexmate.claude.config.apply' currently requires apiKey unconditionally, which
conflicts with applyToClaudeSettings() that allows targetApi: 'ollama' without
an API key; update the schema so apiKey is optional by removing it from the
top-level required array and add a JSON Schema conditional (if/then) that only
requires apiKey when targetApi is not 'ollama' (or equivalently, require apiKey
when targetApi equals the non-ollama value(s)); reference the inputSchema object
for 'codexmate.claude.config.apply' and the applyToClaudeSettings() behavior
when making this change.
♻️ Duplicate comments (1)
cli.js (1)

9360-9378: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail fast when chat_completions has no OpenAI-compatible upstream.

targetApi === 'chat_completions' still falls back to the Anthropic Messages base URL and passes it as upstreamBaseUrl. That silently starts the proxy against the wrong wire API instead of rejecting the config or letting runtime-side provider resolution take over.

Suggested fix
-        const baseUrl = (config.baseUrl || (targetApi === 'ollama' ? 'http://127.0.0.1:11434' : 'https://open.bigmodel.cn/api/anthropic')).trim();
+        const configuredBaseUrl = typeof config.baseUrl === 'string' ? config.baseUrl.trim() : '';
+        const baseUrl = (configuredBaseUrl || (targetApi === 'ollama' ? 'http://127.0.0.1:11434' : 'https://open.bigmodel.cn/api/anthropic')).trim();
         const model = (config.model || DEFAULT_CLAUDE_MODEL).trim();
         let settingsBaseUrl = baseUrl;
         let settingsApiKey = apiKey;
         let proxyResult = null;

         if (targetApi === 'chat_completions' || targetApi === 'ollama') {
+            if (targetApi === 'chat_completions' && !configuredBaseUrl && !(typeof config.name === 'string' && config.name.trim())) {
+                return { success: false, mode: 'claude-proxy', error: 'chat_completions 模式需要显式的上游 Base URL 或可解析的 provider 名称' };
+            }
             await stopBuiltinClaudeProxyRuntime();
             const proxyToken = crypto.randomBytes(24).toString('hex');
             proxyResult = await startBuiltinClaudeProxyRuntime({
                 enabled: true,
                 host: DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS.host,
@@
-                upstreamBaseUrl: baseUrl,
+                ...(configuredBaseUrl ? { upstreamBaseUrl: configuredBaseUrl } : {}),
                 upstreamApiKey: apiKey
             });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli.js` around lines 9360 - 9378, When targetApi === 'chat_completions' the
code currently uses the Anthropic default baseUrl and blindly starts the
built-in proxy with that upstreamBaseUrl; instead, in the block that calls
stopBuiltinClaudeProxyRuntime() and startBuiltinClaudeProxyRuntime() validate
that baseUrl (or config.baseUrl) is OpenAI-compatible before starting the proxy.
If the upstream is not OpenAI-compatible, reject/fail fast (throw or return an
error) or skip starting the proxy and allow runtime-side provider resolution to
proceed; update the logic around targetApi, upstreamBaseUrl, and
startBuiltinClaudeProxyRuntime to either set upstreamBaseUrl only when it's
OpenAI-compatible or to error out when targetApi === 'chat_completions' and no
OpenAI-compatible upstream is provided.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@cli.js`:
- Around line 15816-15829: The MCP tool's inputSchema for action
'codexmate.claude.config.apply' currently requires apiKey unconditionally, which
conflicts with applyToClaudeSettings() that allows targetApi: 'ollama' without
an API key; update the schema so apiKey is optional by removing it from the
top-level required array and add a JSON Schema conditional (if/then) that only
requires apiKey when targetApi is not 'ollama' (or equivalently, require apiKey
when targetApi equals the non-ollama value(s)); reference the inputSchema object
for 'codexmate.claude.config.apply' and the applyToClaudeSettings() behavior
when making this change.

---

Duplicate comments:
In `@cli.js`:
- Around line 9360-9378: When targetApi === 'chat_completions' the code
currently uses the Anthropic default baseUrl and blindly starts the built-in
proxy with that upstreamBaseUrl; instead, in the block that calls
stopBuiltinClaudeProxyRuntime() and startBuiltinClaudeProxyRuntime() validate
that baseUrl (or config.baseUrl) is OpenAI-compatible before starting the proxy.
If the upstream is not OpenAI-compatible, reject/fail fast (throw or return an
error) or skip starting the proxy and allow runtime-side provider resolution to
proceed; update the logic around targetApi, upstreamBaseUrl, and
startBuiltinClaudeProxyRuntime to either set upstreamBaseUrl only when it's
OpenAI-compatible or to error out when targetApi === 'chat_completions' and no
OpenAI-compatible upstream is provided.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8710ccd6-8260-45da-8a0b-5de00b139384

📥 Commits

Reviewing files that changed from the base of the PR and between 7b49467 and 5a5af30.

📒 Files selected for processing (21)
  • cli.js
  • cli/claude-proxy.js
  • tests/e2e/test-claude-proxy.js
  • tests/e2e/test-claude.js
  • tests/unit/claude-proxy-adapter.test.mjs
  • tests/unit/claude-settings-sync.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/app.js
  • web-ui/logic.claude.mjs
  • web-ui/logic.runtime.mjs
  • web-ui/modules/app.methods.claude-config.mjs
  • web-ui/modules/app.methods.session-timeline.mjs
  • web-ui/modules/app.methods.startup-claude.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/partials/index/modals-basic.html
  • web-ui/partials/index/panel-config-claude.html
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/styles/controls-forms.css
💤 Files with no reviewable changes (3)
  • web-ui/modules/app.methods.session-timeline.mjs
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/logic.runtime.mjs
✅ Files skipped from review due to trivial changes (3)
  • web-ui/modules/i18n/locales/zh.mjs
  • tests/unit/claude-settings-sync.test.mjs
  • web-ui/modules/i18n/locales/ja.mjs
🚧 Files skipped from review as they are similar to previous changes (9)
  • web-ui/partials/index/panel-config-claude.html
  • web-ui/app.js
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/app.methods.startup-claude.mjs
  • web-ui/logic.claude.mjs
  • web-ui/partials/index/modals-basic.html
  • web-ui/modules/app.methods.claude-config.mjs
  • tests/e2e/test-claude.js
  • tests/e2e/test-claude-proxy.js
📜 Review details
🔇 Additional comments (20)
web-ui/modules/i18n/locales/en.mjs (1)

1094-1100: LGTM!

tests/unit/web-ui-logic.test.mjs (1)

514-525: LGTM!

Also applies to: 982-983, 1003-1004

web-ui/styles/controls-forms.css (1)

409-413: LGTM!

Also applies to: 441-532

cli/claude-proxy.js (12)

87-114: LGTM!


135-141: LGTM!

Also applies to: 163-165


277-364: LGTM!


366-423: LGTM!


427-538: LGTM!


693-740: LGTM!


916-921: LGTM!


995-1024: LGTM!


1056-1082: LGTM!


1283-1284: LGTM!

Also applies to: 1307-1307


1342-1373: LGTM!


1472-1472: LGTM!

Also applies to: 1521-1521, 1543-1546

tests/unit/claude-proxy-adapter.test.mjs (5)

1-14: LGTM!


69-90: LGTM!


132-169: LGTM!


171-227: LGTM!


296-317: LGTM!

@awsl233777 awsl233777 changed the title feat: add Claude chat completions proxy mode feat: add Claude proxy target modes and config UI Jun 1, 2026
@awsl233777

Copy link
Copy Markdown
Collaborator Author

Follow-up E2E pass completed after the previous update.

What the end-to-end run covered:

  • Started the built-in Claude-compatible proxy as a real local HTTP server.
  • Exercised /health, /v1/models, /v1/messages, and stream SSE.
  • Used capture upstreams for Responses and Chat Completions to verify the actual HTTP bodies crossing the service boundary: image payloads and tool schemas are preserved, while thinking / document blocks are not leaked upstream as fake user text.
  • Used the real Ollama endpoint 27.154.56.162:11434: proxy /v1/models reached upstream /api/tags; proxy /v1/messages reached upstream /api/chat; gemma4:31b-cloud returned codexmate-ollama-ok through the full Claude→Ollama→Anthropic roundtrip.

The E2E run caught a real bug: Ollama upstream paths were being built as /v1/api/tags and /v1/api/chat, which returns 404 on Ollama. Fixed in 881a179 by routing Ollama target paths to native /api/* without injecting /v1, and added a regression test that asserts the proxy calls exactly GET /api/tags and POST /api/chat.

Validation after the fix:

  • npm run test:unit — 584 tests passed.
  • npm run lint — passed for 211 files.
  • E2E proxy script — Responses, Chat Completions, and real Ollama target all passed.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

@awsl233777 Great catch on the Ollama path bug — /v1/api/tags silently routing to 404 is exactly the kind of issue that only surfaces against a real upstream. The fix in 881a179 (stripping the /v1 prefix injection for Ollama so paths resolve to native /api/tags and /api/chat) is the right approach, and adding the regression test that asserts the exact upstream paths (GET /api/tags, POST /api/chat) will prevent regressions from future refactors. Triggering a full review now. 🐇

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

# Conflicts:
#	web-ui/modules/app.methods.claude-config.mjs
#	web-ui/modules/i18n/locales/vi.mjs
@awsl233777

Copy link
Copy Markdown
Collaborator Author

Resolved the merge conflicts with origin/main and pushed merge commit a2c5efc.

Conflict files handled:

  • web-ui/modules/app.methods.claude-config.mjs
    • Preserved the PR behavior that targetApi: ollama does not require an API key.
    • Preserved the main-branch i18n changes for Claude validation/toast messages.
  • web-ui/modules/i18n/locales/vi.mjs
    • Kept the Claude target API Vietnamese strings.
    • Kept the new main-branch toast/validation/modal Vietnamese strings.
  • tests/unit/claude-settings-sync.test.mjs
    • Updated the affected Ollama no-key tests to include the i18n helper context introduced by main.

Validation run locally after resolving conflicts:

  • node --check web-ui/modules/app.methods.claude-config.mjs
  • node --check web-ui/modules/i18n/locales/vi.mjs
  • git diff --check
  • npm run test:unit — All 616 tests passed.
  • npm run lint — Lint passed for 315 files.
  • npm run test:e2eE2E_EXIT:0

GitHub now reports the PR as mergeable (MERGEABLE), with checks and CodeRabbit re-running on the new head.

@coderabbitai re-review

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
tests/unit/claude-proxy-adapter.test.mjs (1)

409-417: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Eliminate TOCTOU port probing to prevent flaky EADDRINUSE failures.

Line 409–Line 417 finds a free port and releases it before startBuiltinClaudeProxyRuntime binds it (e.g., Line 449, Line 537, Line 616), which creates a race window. Please switch to a retry-on-EADDRINUSE wrapper for startup (or support port: 0 in runtime and assert the returned listenUrl).

💡 Minimal test-side fix (retry wrapper)
+async function startProxyWithRetry(controller, payload, attempts = 4) {
+    let last = null;
+    for (let i = 0; i < attempts; i += 1) {
+        const result = await controller.startBuiltinClaudeProxyRuntime(payload);
+        if (result && result.success) return result;
+        const msg = result && result.error ? String(result.error) : '';
+        if (!/EADDRINUSE/i.test(msg) || i === attempts - 1) return result;
+        await new Promise((r) => setTimeout(r, 30 * (i + 1)));
+        last = result;
+    }
+    return last || { error: 'failed to start proxy' };
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/claude-proxy-adapter.test.mjs` around lines 409 - 417, The current
findFreePortForTest() probes and releases a port causing TOCTOU races; instead
change the test to remove findFreePortForTest() usage and either (A) call
startBuiltinClaudeProxyRuntime with port: 0 (or update runtime to accept port:
0) and assert the returned listenUrl contains the actual bound port, or (B) wrap
startBuiltinClaudeProxyRuntime calls in a short retry-on-EADDRINUSE loop (catch
EADDRINUSE, wait a few ms, retry) so the runtime binds successfully; update
tests that call startBuiltinClaudeProxyRuntime (see invocations around lines
where startBuiltinClaudeProxyRuntime is used) to use the chosen approach and
remove the separate free-port allocation to eliminate the race window.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@tests/unit/claude-proxy-adapter.test.mjs`:
- Around line 409-417: The current findFreePortForTest() probes and releases a
port causing TOCTOU races; instead change the test to remove
findFreePortForTest() usage and either (A) call startBuiltinClaudeProxyRuntime
with port: 0 (or update runtime to accept port: 0) and assert the returned
listenUrl contains the actual bound port, or (B) wrap
startBuiltinClaudeProxyRuntime calls in a short retry-on-EADDRINUSE loop (catch
EADDRINUSE, wait a few ms, retry) so the runtime binds successfully; update
tests that call startBuiltinClaudeProxyRuntime (see invocations around lines
where startBuiltinClaudeProxyRuntime is used) to use the chosen approach and
remove the separate free-port allocation to eliminate the race window.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 141e1b2d-c5ab-4c3c-acdb-ac4aa53b8f7d

📥 Commits

Reviewing files that changed from the base of the PR and between f9f12b8 and a2c5efc.

📒 Files selected for processing (32)
  • cli.js
  • cli/claude-proxy.js
  • cli/update.js
  • tests/e2e/test-claude-proxy.js
  • tests/e2e/test-claude.js
  • tests/unit/claude-proxy-adapter.test.mjs
  • tests/unit/claude-settings-sync.test.mjs
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/install-target-cards.test.mjs
  • tests/unit/provider-share-command.test.mjs
  • tests/unit/web-ui-behavior-parity.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/app.js
  • web-ui/logic.claude.mjs
  • web-ui/logic.runtime.mjs
  • web-ui/modules/app.methods.claude-config.mjs
  • web-ui/modules/app.methods.index.mjs
  • web-ui/modules/app.methods.install.mjs
  • web-ui/modules/app.methods.session-actions.mjs
  • web-ui/modules/app.methods.session-timeline.mjs
  • web-ui/modules/app.methods.startup-claude.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/partials/index/layout-header.html
  • web-ui/partials/index/modals-basic.html
  • web-ui/partials/index/panel-config-claude.html
  • web-ui/partials/index/panel-config-codex.html
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/styles/controls-forms.css
  • web-ui/styles/layout-shell.css
💤 Files with no reviewable changes (2)
  • web-ui/modules/app.methods.session-timeline.mjs
  • web-ui/logic.runtime.mjs
👮 Files not reviewed due to content moderation or server errors (1)
  • cli.js
📜 Review details
🔇 Additional comments (23)
web-ui/styles/controls-forms.css (1)

486-486: LGTM!

Also applies to: 488-488, 493-495

web-ui/partials/index/panel-config-codex.html (1)

37-37: LGTM!

Also applies to: 207-207

cli/update.js (1)

67-104: LGTM!

Also applies to: 189-190

web-ui/modules/app.methods.install.mjs (1)

1-3: LGTM!

Also applies to: 20-100

web-ui/modules/app.methods.index.mjs (1)

92-92: LGTM!

web-ui/partials/index/layout-header.html (1)

113-124: LGTM!

web-ui/styles/layout-shell.css (1)

510-563: LGTM!

tests/unit/install-target-cards.test.mjs (1)

45-78: LGTM!

tests/unit/web-ui-behavior-parity.test.mjs (1)

339-341: LGTM!

Also applies to: 375-377, 597-603

web-ui/res/web-ui-render.precompiled.js (1)

166-181: LGTM!

Also applies to: 976-976, 1602-1602, 1925-1934, 1938-1953, 5831-5844, 5984-5997

web-ui/modules/app.methods.session-actions.mjs (1)

415-419: LGTM!

Also applies to: 423-424

tests/unit/provider-share-command.test.mjs (1)

377-413: LGTM!

Also applies to: 429-461

tests/e2e/test-claude.js (1)

1-2: LGTM!

Also applies to: 6-6, 48-56, 87-126, 132-150

web-ui/app.js (1)

273-275: LGTM!

Also applies to: 283-283, 289-289, 297-299, 562-569, 646-648

web-ui/modules/app.methods.startup-claude.mjs (1)

259-260: LGTM!

web-ui/modules/i18n/locales/vi.mjs (1)

109-114: LGTM!

Also applies to: 245-253

tests/unit/claude-settings-sync.test.mjs (1)

15-15: LGTM!

Also applies to: 335-367, 393-395, 647-651, 712-778, 820-872, 982-984, 1003-1005, 1315-1337

web-ui/logic.claude.mjs (1)

72-78: LGTM!

Also applies to: 85-87, 167-169

web-ui/modules/app.methods.claude-config.mjs (1)

26-30: LGTM!

Also applies to: 43-45, 64-67, 96-99, 123-127, 154-156, 168-172, 184-185, 198-202, 206-207, 215-218, 241-245, 288-289, 295-298, 317-321

web-ui/modules/i18n/locales/en.mjs (1)

148-153: LGTM!

Also applies to: 1146-1153

web-ui/modules/i18n/locales/ja.mjs (1)

149-154: LGTM!

Also applies to: 1139-1146

tests/unit/config-tabs-ui.test.mjs (1)

138-140: LGTM!

Also applies to: 148-149, 290-305

tests/unit/web-ui-logic.test.mjs (1)

54-55: LGTM!

Also applies to: 66-67, 78-79, 84-88, 233-239, 522-533

@awsl233777

Copy link
Copy Markdown
Collaborator Author

Fixed the refresh/import regression for builtin Claude transform providers.

What changed:

  • Treat Claude settings that point at the generated loopback proxy (http://127.0.0.1:* / localhost, 48-hex temporary token) as Codex Mate builtin Claude proxy settings instead of an external Claude provider.
  • Prefer the currently selected builtin transform config when the proxy settings model matches, so refresh keeps Local Ollama / chat-completions selections stable.
  • Keep normal external Claude settings import intact.

Validation run locally:

  • npm run lint
  • npm run test:unit ✅ (All 618 tests passed)
  • npm run test:e2e ✅ (E2E_EXIT:0)
  • Real browser refresh regression check with isolated HOME + headless Chrome ✅
    • Control: external https://open.bigmodel.cn/api/anthropic settings were imported as 导入-open.bigmodel.cn, proving the startup refresh/import path ran.
    • Builtin Ollama proxy: after applying Local Ollama, Claude settings used http://127.0.0.1:<port> plus a generated 48-hex token; after page refresh, the browser still had only Local Ollama and no 导入-* duplicate.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

@awsl233777 Thanks for the regression fix! The loopback-detection heuristic (matching http://127.0.0.1:*/localhost base URL plus a 48-hex temporary token) is a clean way to distinguish builtin proxy settings from genuine external Claude providers — without it every page refresh would spawn a spurious 导入-* duplicate for users running any of the three proxy modes. The "prefer currently selected builtin transform config when model matches" preference logic is also the right call to keep the UI stable across refreshes. The isolated-HOME control test (confirming external bigmodel.cn settings still import normally) is good evidence the guard doesn't over-match. Triggering a full review now. 🐇

✅ Action performed

Full review finished.

@awsl233777

Copy link
Copy Markdown
Collaborator Author

Follow-up pushed in 41fac7d for the CodeRabbit test stability note.

What changed:

  • Added a startBuiltinClaudeProxyRuntimeForTest() helper that allocates the test proxy port at start time and retries briefly on EADDRINUSE / address already in use.
  • Updated the three unit tests that start the builtin Claude proxy (/api Ollama path routing, Ollama upstream error mapping, and saved share-import restart) to use the retrying helper instead of relying on a single preselected proxy port.

Validation actually run locally after this follow-up:

  • npm run test:unit ✅ (All 618 tests passed)
  • npm run lint ✅ (Lint passed for 319 file(s))
  • npm run test:e2e ✅ (E2E_EXIT:0)

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

@awsl233777 Thanks for the test stability follow-up! The startBuiltinClaudeProxyRuntimeForTest() helper with EADDRINUSE retry logic is a solid improvement — port conflicts in parallel/repeated test runs are a common source of flakiness for integration-style unit tests that spin up real HTTP servers. Updating all three affected tests (Ollama path routing, upstream error mapping, and saved share-import restart) to use the retrying helper ensures consistent coverage without timing-sensitive port assumptions. Triggering a full review now. 🐇

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@awsl233777

Copy link
Copy Markdown
Collaborator Author

Additional browser regression rerun on latest head 41fac7d completed after the test-side port hardening follow-up.

Browser evidence from an isolated environment:

  • Used isolated HOME=/tmp/codexmate-pr171-browser and isolated Chrome user data dir.
  • Control path: external Claude settings refresh still imports the real external provider as 导入-open.bigmodel.cn.
  • Fixed path: builtin Ollama → Claude proxy refresh keeps only Local Ollama; importedKeys is empty after reload.
  • Verified generated Claude settings still point at the local builtin proxy URL and use a generated 48-hex proxy token.
  • Screenshot artifact updated locally at tmp/browser-pr171-claude-refresh-final.png.

Post-browser cleanup validation:

  • Confirmed the isolated browser/server process cleanup completed.
  • Re-ran npm run test:e2e after cleanup ✅ (E2E_EXIT:0).

@awsl233777

Copy link
Copy Markdown
Collaborator Author

Documentation follow-up pushed in fee14cc.

What changed:

  • Added the Claude Provider Bridge to README.md / README.zh.md under the special-features section.
  • Added the matching Current Progress / 当前进展 row for Claude Code support through the built-in Claude-compatible proxy.
  • Scope is docs-only: README.md and README.zh.md.

Validation actually run for this docs-only follow-up:

  • git diff --check
  • Verified PR head via GitHub API: fee14ccd7a20b0f07aa7beeb20357e8c7a3cf078

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

@awsl233777 Thanks for the docs follow-up! Adding the Claude Provider Bridge to both README.md and README.zh.md under the special-features section — along with the Current Progress row for Claude Code support through the built-in proxy — gives users a clear entry point to discover the new capability. Triggering a full review of fee14cc now. 🐇

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@awsl233777

Copy link
Copy Markdown
Collaborator Author

Follow-up pushed in 199419c for the mobile version-status layout regression.

What changed:

  • Kept the desktop side rail version status behavior.
  • Added a compact mobile brand/status strip so narrow viewports still show Codex Mate vX.Y.Z and the latest-version state even when the side rail is hidden.
  • Expanded the version-status API payload with updateAvailable, source, checkedAt, and cached status metadata.
  • Added force-refresh behavior for manual retry so the retry action does not reuse the cache.
  • Added en/zh/ja/vi strings for checking/current/retry states.

Browser verification actually run:

  • Mobile viewport 390x844: before screenshot confirmed side rail hidden and version status not visible; after screenshot confirmed mobile bar visible with Codex Mate v0.0.43 + 已是最新.
  • Desktop viewport: confirmed the existing left-side rail still shows Codex Mate v0.0.43 and 已是最新 / 当前 v0.0.43 → 最新 v0.0.43.
  • Screenshot artifacts are saved locally under tmp/: browser-mobile-layout-current.png, browser-mobile-layout-after.png, browser-desktop-layout-after.png.

Validation actually run:

  • npm run test:unit ✅ (All 620 tests passed)
  • npm run lint ✅ (Lint passed for 216 file(s))
  • npm run test:e2e ✅ (E2E_EXIT:0)
  • Re-ran npm run test:e2e after browser cleanup ✅ (E2E_EXIT:0)

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

@awsl233777 Thanks for the mobile layout follow-up! The changes in 199419c look well-considered:

  • Compact mobile brand/status strip — showing Codex Mate vX.Y.Z + version state on narrow viewports where the side rail is hidden fills the visibility gap cleanly without disrupting the desktop layout.
  • Expanded version-status payload — adding updateAvailable, source, checkedAt, and cached status metadata gives callers richer context and enables smarter UI decisions (e.g., distinguishing a cached "up to date" from a freshly checked one).
  • Force-refresh on manual retry — bypassing the cache on explicit user action is the right UX contract; stale data should only be tolerated for background/silent checks, not user-initiated ones.
  • i18n completeness — covering checking/current/retry states across all four locales (en/zh/ja/vi) keeps the UI consistent.

The browser verification with before/after screenshots on both mobile (390×844) and desktop viewports, plus the 620 unit + e2e test pass, provides solid confidence. Triggering a full review now. 🐇

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cli/update.js`:
- Around line 135-137: The cache check currently only validates TTL so
latestVersionStatusCache can be returned for a different currentVersion; modify
the logic to include the currentVersion in the cache validation: when storing
into latestVersionStatusCache include the input currentVersion (e.g.,
checkedCurrentVersion) and when reading ensure now -
latestVersionStatusCache.checkedAtMs < cacheTtlMs AND
latestVersionStatusCache.checkedCurrentVersion === currentVersion before
returning the cached payload; apply the same change to the other cache-return
site around lines 149-150 so cached results are only reused for the same
currentVersion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 17c5ddc8-7319-43bd-8639-a52a90438591

📥 Commits

Reviewing files that changed from the base of the PR and between fee14cc and 199419c.

📒 Files selected for processing (17)
  • cli.js
  • cli/update.js
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/install-target-cards.test.mjs
  • tests/unit/run.mjs
  • tests/unit/update-version-status.test.mjs
  • tests/unit/web-ui-behavior-parity.test.mjs
  • web-ui/app.js
  • web-ui/modules/app.methods.install.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/partials/index/layout-header.html
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/styles/layout-shell.css
  • web-ui/styles/responsive.css
✅ Files skipped from review due to trivial changes (7)
  • web-ui/styles/responsive.css
  • tests/unit/update-version-status.test.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/res/web-ui-render.precompiled.js
🚧 Files skipped from review as they are similar to previous changes (6)
  • tests/unit/config-tabs-ui.test.mjs
  • web-ui/styles/layout-shell.css
  • tests/unit/web-ui-behavior-parity.test.mjs
  • web-ui/app.js
  • web-ui/modules/app.methods.install.mjs
  • cli.js
📜 Review details
🔇 Additional comments (4)
cli/update.js (1)

106-126: LGTM!

Also applies to: 237-240

tests/unit/install-target-cards.test.mjs (1)

49-155: LGTM!

tests/unit/run.mjs (1)

57-57: LGTM!

web-ui/partials/index/layout-header.html (1)

2-13: LGTM!

Also applies to: 126-130

Comment thread cli/update.js
Comment on lines +135 to +137
if (latestVersionStatusCache && cacheTtlMs > 0 && now - latestVersionStatusCache.checkedAtMs < cacheTtlMs) {
return { ...latestVersionStatusCache.payload, cached: true };
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Cache payload is reused across different currentVersion inputs.

At Line 135, cache reuse only checks TTL. A call for version A can incorrectly return cached payload for version B within TTL, including wrong currentVersion and updateAvailable.

Suggested fix
 let latestVersionStatusCache = null;
 
 async function fetchLatestVersionStatus(options = {}) {
     const currentVersion = normalizePackageVersion(options.currentVersion) || String(options.currentVersion || '');
+    const cacheKey = currentVersion;
     const timeoutMs = Number.isFinite(Number(options.timeoutMs)) ? Number(options.timeoutMs) : 5000;
     const cacheTtlMs = Number.isFinite(Number(options.cacheTtlMs)) ? Math.max(0, Number(options.cacheTtlMs)) : 10 * 60 * 1000;
     const now = typeof options.now === 'function' ? options.now() : Date.now();
-    if (latestVersionStatusCache && cacheTtlMs > 0 && now - latestVersionStatusCache.checkedAtMs < cacheTtlMs) {
+    if (
+        latestVersionStatusCache &&
+        latestVersionStatusCache.cacheKey === cacheKey &&
+        cacheTtlMs > 0 &&
+        now - latestVersionStatusCache.checkedAtMs < cacheTtlMs
+    ) {
         return { ...latestVersionStatusCache.payload, cached: true };
     }
@@
-    latestVersionStatusCache = { checkedAtMs: now, payload };
+    latestVersionStatusCache = { checkedAtMs: now, cacheKey, payload };
     return payload;
 }

Also applies to: 149-150

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/update.js` around lines 135 - 137, The cache check currently only
validates TTL so latestVersionStatusCache can be returned for a different
currentVersion; modify the logic to include the currentVersion in the cache
validation: when storing into latestVersionStatusCache include the input
currentVersion (e.g., checkedCurrentVersion) and when reading ensure now -
latestVersionStatusCache.checkedAtMs < cacheTtlMs AND
latestVersionStatusCache.checkedCurrentVersion === currentVersion before
returning the cached payload; apply the same change to the other cache-return
site around lines 149-150 so cached results are only reused for the same
currentVersion.

@awsl233777

Copy link
Copy Markdown
Collaborator Author

Version follow-up pushed in 720cf32.

What changed:

  • Set the package version back to 0.0.42 in package.json.
  • Updated the root package metadata in package-lock.json to 0.0.42.
  • Confirmed remote tags already include both v0.0.42 and v0.0.43; no existing git tag was rewritten or deleted.

Verification actually run before push:

  • npm run test:unit ✅ (All 620 tests passed)
  • npm run lint ✅ (Lint passed for 216 file(s))
  • npm run test:e2e ✅ (E2E_EXIT:0)
  • Browser screenshots after the version change:
    • mobile shows Codex Mate v0.0.42 + 有新版本 v0.0.43
    • desktop shows Codex Mate v0.0.42 + 当前 v0.0.42 → 最新 v0.0.43
  • Re-ran npm run test:e2e after browser cleanup ✅ (E2E_EXIT:0)

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

@awsl233777 Thanks for the version correction! Rolling back to 0.0.42 in package.json and package-lock.json is the right call — it means the update-notice feature will immediately demonstrate itself in action when this branch is published (current 0.0.42 → latest 0.0.43), which is a nice built-in smoke test of the version-status UI. The browser screenshots confirming mobile (有新版本 v0.0.43) and desktop (当前 v0.0.42 → 最新 v0.0.43) rendering look exactly right. Triggering a full review now. 🐇

✅ Action performed

Full review finished.

@awsl233777

Copy link
Copy Markdown
Collaborator Author

Version correction pushed in b4d0ab3.

What changed:

  • Set the package version to 0.0.44 in package.json.
  • Updated the root package metadata in package-lock.json to 0.0.44.
  • Created and pushed git tag v0.0.44; the tag points to commit b4d0ab38ec1e627012327c08f7d581d7687fe603.

Verification actually run before push:

  • npm run test:unit ✅ (All 620 tests passed)
  • npm run lint ✅ (Lint passed for 218 file(s))
  • npm run test:e2e ✅ (E2E_EXIT:0)
  • Browser regression for Claude built-in proxy refresh on the current worktree ✅
    • external Claude settings still import BigModel config
    • built-in Ollama proxy refresh keeps only Local Ollama
    • generated Claude settings point to local proxy 127.0.0.1:8328 with token shape validated
  • Browser screenshots after the 0.0.44 version change ✅
    • mobile shows Codex Mate v0.0.44 + 已是最新
    • desktop shows Codex Mate v0.0.44 + 当前 v0.0.44 → 最新 v0.0.43
  • Re-ran npm run test:e2e after browser cleanup ✅ (E2E_EXIT:0)

Note: npm latest is still 0.0.43 at verification time, so the version-status API correctly reports currentVersion=0.0.44, latestVersion=0.0.43, and updateAvailable=false.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

@awsl233777 Thanks for the version bump! Setting 0.0.44 as the package version with the version-status API correctly reporting updateAvailable: false (since 0.0.44 > 0.0.43 npm latest) is the right state — the update-notice UI will stay quiet for users already on the latest, while users still on 0.0.43 will see the nudge. The browser regression confirmation (builtin proxy refresh keeping only Local Ollama, external BigModel still importing normally, token shape validated) is good evidence the loopback-detection heuristic from the previous commit is still holding on this head. Triggering a full review now. 🐇

✅ Action performed

Full review finished.

@awsl233777 awsl233777 force-pushed the feat/claude-chat-completions-proxy branch from b4d0ab3 to e62ffc0 Compare June 4, 2026 16:46
@awsl233777

Copy link
Copy Markdown
Collaborator Author

Cleanup pushed in e62ffc0.

What changed after the previous version correction:

  • Squashed the last two version commits into one clean commit: chore: set package version to 0.0.44.
  • The intermediate accidental 0.0.42 commit is no longer present in the PR branch history.
  • Moved and force-updated git tag v0.0.44; it now points to e62ffc06a9f95fb4379b6c3b1ebe4225ac2ab44c.
  • Package metadata remains 0.0.44 in both package.json and package-lock.json.

Validation already run before this squash (tree content unchanged except commit history/tag pointer):

  • npm run test:unit ✅ (All 620 tests passed)
  • npm run lint ✅ (Lint passed for 218 file(s))
  • npm run test:e2e ✅ (E2E_EXIT:0)
  • Browser regression for Claude built-in proxy refresh ✅
  • Mobile/desktop browser version-status screenshots for 0.0.44
  • Post-browser cleanup npm run test:e2e ✅ (E2E_EXIT:0)

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

@awsl233777 Thanks for the history cleanup! Squashing the intermediate 0.0.42 commit and force-updating v0.0.44 to e62ffc0 keeps the branch history clean without any tree content change — all the previously validated behavior (620 unit tests, lint, e2e, browser regression) still applies. Triggering a full review now. 🐇

✅ Action performed

Full review finished.

@awsl233777

Copy link
Copy Markdown
Collaborator Author

Release workflow fix pushed in 1bc29bd.

What changed:

  • tools/release/changelog.js now generates release notes in a release-body format based on the actual semver tag diff.
  • The generated notes end with a Contributors section.
  • .github/workflows/release.yml now passes body_path: ${{ env.RELEASE_CHANGELOG_FILE }} to softprops/action-gh-release and disables GitHub auto-generated notes with generate_release_notes: false.
  • Added a unit assertion so the workflow cannot silently regress back to auto-generated release notes.
  • Moved v0.0.44 to the new head commit so the tag contains the fixed workflow and changelog script.

Validation actually run:

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

@awsl233777 Thanks for the release workflow fix! The changes in 1bc29bd are well-structured:

  • changelog.js generator — producing release notes from the actual semver tag diff (rather than relying on GitHub's auto-generated notes) gives you full control over the release body format and the Contributors section footer.
  • body_path + generate_release_notes: false — the combination ensures the custom notes are used exclusively without any auto-generated content appended.
  • Unit assertion against workflow regression — preventing a silent rollback to auto-generated notes in a future edit is a smart safeguard; release workflow regressions are often invisible until a release is published.
  • Tag moved to new head — ensures the published v0.0.44 release already includes the fixed workflow, which matters for any future patch release that re-runs the same workflow template.

The live release at v0.0.44 with both assets present and the generated body ending with Contributors confirms the full path worked end-to-end. Triggering a full review now. 🐇


🧠 Learnings used
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-30T04:47:15.352Z
Learning: In PR `#55` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-04T01:36:26.964Z
Learning: In PR `#68` for SakuraByteCore/codexmate, github-actions[bot] again flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`, `#68`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-31T12:37:51.023Z
Learning: In PR `#59` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-03T10:42:22.368Z
Learning: In PR `#67` for SakuraByteCore/codexmate, github-actions[bot] flagged that CodeRabbit was again introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
✅ Action performed

Full review finished.

@ymkiux ymkiux merged commit 628d451 into main Jun 4, 2026
13 checks passed
@ymkiux ymkiux deleted the feat/claude-chat-completions-proxy branch June 4, 2026 17:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants